import 'dart:convert';

import 'package:im_app/im/util/MapUtil.dart';
import 'package:logger/logger.dart';
import 'package:sqflite/sqflite.dart';

import '../pojo/ImPojo.dart';
import '../util/DateUtil.dart';
import '../util/ImConstant.dart';
import '../util/ImSingleton.dart';
import 'BaseDao.dart';
import 'ImGroupDao.dart';
import 'ImUserDao.dart';

class ImSessionDao extends BaseDao {

  final _logger = Logger(printer: PrettyPrinter(),);

  Future<ImSession?> getById(int id) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    List? list = await database?.rawQuery("select * from im_session where id = ?", [id]);
    if (list != null && list.isNotEmpty) {
      return Future.value(_toImSession(list[0]));
    }
    return null;
  }

  Future<ImSession?> getByServerIdAndUserId(String serverId, String userId) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    List? list = await database?.rawQuery("select * from im_session where serverId = ? and currentUserId = ?",
        [serverId, userId]);
    if (list != null && list.isNotEmpty) {
      return Future.value(_toImSession(list[0]));
    }
    return null;
  }

  Future<ImSession?> getByGroupCodeAndUserId(String groupCode, String userId) async {
    Database? database = getDatabase(); //await getReadOnlyDatabase(); //ImSingleton.getInstance()?.dataBase;
    List? list = await database?.rawQuery("select * from im_session where groupCode =? and currentUserId = ? limit 1",
        [groupCode, userId]);
    if (list != null && list.isNotEmpty) {
      return Future.value(_toImSession(list[0]));
    }
    return Future.value(null);
  }

  Future<ImSession?> getByUserId(String currentUserId, String fromUserId, String toUserId) async {
    Database? database = getDatabase(); // await getReadOnlyDatabase(); //ImSingleton.getInstance()?.dataBase;
    List? list = await database?.rawQuery("select * from im_session where currentUserId = ? and ((fromUserId = ? and toUserId = ?) or (fromUserId = ? and toUserId = ?)) limit 1",
        [currentUserId, fromUserId, toUserId, toUserId, fromUserId]);
    if (list != null && list.isNotEmpty) {
      return Future.value(_toImSession(list[0]));
    }
    return Future.value(null);
  }

  Future<List<ImSession>> getByUserIdAndSize(String currentUserId, int size) async {
    int preDay = ImConstant.IM_SESSION_SHOW_DAYS;
    DateTime localDateTime = DateTime.now().add(Duration(days: -preDay));
    return await getByUserIdAndTimeAndSize(currentUserId, localDateTime, 0, size);
  }

  Future<List<ImSession>> getByUserIdAndTimeAndSize(String currentUserId, DateTime startDate, int start, int size) async {
    Database? database = getDatabase(); //await getReadOnlyDatabase(); //ImSingleton.getInstance()?.dataBase;
    String sql = '''select * from im_session where currentUserId = ? and updateTime >= ? and status
      not in (0, 2) order by hotMessageTotal desc, updateTime desc limit $start, $size''';
    List<Map<String, Object?>>? list = await database?.rawQuery(sql, [currentUserId, DateUtil.formatDate(startDate)]);
    if (list == null) {
      return [];
    } else {
      List<ImSession> result = list.map((e) => _toImSession(e)).toList();
      return result;
    }
  }

  Future<List<ImSession>> getByUserIds(String currentUserId, List<String?>? userIdList) async {
    if(userIdList == null || userIdList.length < 1) return [];
    Database? database = getDatabase();
    String param = userIdList.map((e) => "\"$e\"").join(",");
    String sql = "select * from im_session where (fromUserId = ? and toUserId in ($param)) or (toUserId = ? and fromUserId in ($param))";
    List<Map<String, Object?>>? list = await database?.rawQuery(sql, [currentUserId, currentUserId]);
    if (list != null && list.isEmpty) {
      return Future.value(list?.map((map)=>_toImSession(map)).toList());
    } else {
      return [];
    }
  }

  Future<List<ImSession>> getByGroupCodesAndUserId(String currentUserId, List<String?>? codeList) async {
    if(codeList == null || codeList.isEmpty) return [];
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    String param = codeList.map((e) => "\"$e\"").join(",");
    String sql = "select * from im_session where fromUserId = ? and groupId in ($param)";
    List<Map<String, Object?>>? list = await database?.rawQuery(sql, [currentUserId]);
    if (list != null && list.isEmpty) {
      return Future.value(list?.map((map)=>_toImSession(map)).toList());
    } else {
      return [];
    }
  }

  Future<int> sumHotMessageTotal(String currentUserId, DateTime? startDate, int size) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var sql = '''select sum(hotMessageTotal) total from im_session where currentUserId = ? and updateTime > ? order by 
        updateTime desc limit 0, $size''';
    List<Map<String, dynamic>>? list = await database?.rawQuery(sql, [currentUserId, DateUtil.formatDate(startDate)]);
    if (list != null && list.length > 0) {
      return list[0]['total'];
    }
    return 0;
  }

  Future<void> batchInserts(List<ImSession> imSessions) async {
    if (imSessions.length < 1) return Future.value();
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    Batch? batch = database?.batch();
    for (var session in imSessions) {
      var data = _toInsertValueMap(session);
      batch?.insert("im_session", data);
    }
    List<Object?>? list = await batch?.commit();
    var idx = 0;
    for (var id in list!) {
      imSessions[idx++].id = id as int;
    }
    _logger.i("batch session json: ${jsonEncode(list)}");
  }

  Future<int> insert(ImSession session) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var data = _toInsertValueMap(session);
    int? id = await database?.insert("im_session", data);
    assert(id != null && id > 0, "insert im_session error");
    session.id = id;
    _logger.i("batch session json: $id");
    return Future.value(id);
  }

  Future<void> batchUpdates(String userId, List<ImSession> imSessions) async {
    if (imSessions.isEmpty) return Future.value();
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    Batch? batch = database?.batch();
    for (var session in imSessions) {
      var data = _toUpdateMap(session);
      if (session.groupCode != null) {
        batch?.update("im_session", data, where: "currentUserId = ? and fromUserId=? and groupCode=?",
            whereArgs:[userId, userId, session.groupCode]);
      } else {
        batch?.update("im_session", data, where: "currentUserId = ? and fromUserId=? and toUserId=?",
            whereArgs:[userId, userId, session.toUserId]);
      }
    }
    List<Object?>? list = await batch?.commit();
    _logger.i("batch sessions json: " + jsonEncode(list));
  }

  Future<void> incHotMessageNum(int sessionId, int hotMessageTotal) async {
    if (hotMessageTotal < 1) return;
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var updateTime = DateUtil.formatDate(DateTime.now());
    String sql = "update im_session set hotMessageTotal = hotMessageTotal + ?, updateTime=? where id = ?";
    var size = await database?.rawUpdate(sql, [hotMessageTotal, updateTime, sessionId]);
    assert(size == 1);
  }

  Future<void> decHotMessageNum(int sessionId, int hotMessageTotal) async {
    if(hotMessageTotal < 1) return;
    _logger.i("decHotMessage $sessionId, total: $hotMessageTotal", time: DateTime.now());
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var updateTime = DateUtil.formatDate(DateTime.now());
    String sql = "update im_session set hotMessageTotal = hotMessageTotal - ?, updateTime=? where id = ? and "
        "hotMessageTotal - ? >= 0";
    var size = await database?.rawUpdate(sql, [hotMessageTotal, updateTime, sessionId, hotMessageTotal]);
    assert(size == 1);
  }

  Future<void> cleanHotMessageNum(int sessionId) async {
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var updateTime = DateUtil.formatDate(DateTime.now());
    String sql = "update im_session set hotMessageTotal = 0, updateTime=? where id = ?";
    var size = await database?.rawUpdate(sql, [updateTime, sessionId]);
    assert(size == 1);
  }

  Future<void> setHotMessageNum(int id, int value) async {
    if (value < 0) return;
    Database? database = getDatabase(); //ImSingleton.getInstance()?.dataBase;
    var updateTime = DateUtil.formatDate(DateTime.now());
    String sql = "update im_session set hotMessageTotal = ?, updateTime=? where id = ?";
    var size = await database?.rawUpdate(sql, [value, updateTime, id]);
    assert(size == 1);
  }

  Future<void> loadGroupInfo(ImSession session) async {
    if (session.groupCode != null) {
      var groupDao = ImGroupDao();
      session.group = await groupDao.getByCodeAndUserId(session.groupCode!, session.currentUserId!);
    } else {
      var userDao = ImUserDao();
      session.toUser = await userDao.getByUserId(session.toUserId!);
    }
  }

  void sort(List<ImSession> sessions) {
    sessions.sort((s1, s2) {
      bool isTop = false, isTop2 = false;

      if (s1.groupCode != null) {
        isTop = s1.group!.currentGroupUser!.isTop??false;
      } else {
        isTop = s1.fromUserId!.compareTo(s1.toUserId!)>=0 ? s1.isTop : s1.isTop2;
      }

      if (s2.groupCode != null) {
        isTop2 = s2.group!.currentGroupUser!.isTop??false;
      } else {
        isTop2 = s2.fromUserId!.compareTo(s2.toUserId!)>=0 ? s2.isTop : s2.isTop2;
      }

      if (isTop && isTop2) {
        int ht = s2.hotMessageTotal!.compareTo(s1.hotMessageTotal!);
        int ut = s2.updateTime!.compareTo(s1.updateTime!);
        if (ht == 0) {
          return ut;
        }
        return ht;
      }

      if (isTop) return -1;
      if (isTop2) return 1;

      int ht = s2.hotMessageTotal!.compareTo(s1.hotMessageTotal!);
      int ut = s2.updateTime.compareTo(s1.updateTime);
      if (ht == 0) {
        return ut;
      }
      return ht;
    });
  }

  Map<String, Object?> _toInsertValueMap(ImSession session) {
    Map<String, Object?> map = {};
    map['currentUserId'] = session.currentUserId;
    map['name'] = session.name!;
    map['groupCode'] = session.groupCode;
    map['fromUserId'] = session.fromUserId;
    map['serverId'] = session.serverId;
    map['toUserId'] = session.toUserId;
    map['hotMessageTotal'] = session.hotMessageTotal;
    map['latelyMessage'] = session.latelyMessage;
    map['isWaking'] = session.isWaking ? 1 : 0;
    map['isTop'] = session.isTop ? 1 : 0;
    map['isDisturb'] = session.isDisturb ? 1 : 0;
    map['isWaking2'] = session.isWaking2 ? 1 : 0;
    map['isTop2'] = session.isTop2 ? 1 : 0;
    map['isDisturb2'] = session.isDisturb2 ? 1 : 0;
    map['sessionType'] = session.sessionType?.value;
    map["status"]= session.status?.value;
    map['ext1'] = session.ext1;
    map['ext2'] = session.ext2;
    map['createTime'] = DateUtil.formatDate(session.createTime);
    map['updateTime'] = DateUtil.formatDate(session.updateTime);

    return map;
  }

  Map<String, Object?> _toUpdateMap(ImSession session) {
    Map<String, Object?> map = {};
    map['name'] = session.name;
    map['serverId'] = session.serverId;
    // map['hotMessageTotal'] = session.hotMessageTotal;
    map['latelyMessage'] = session.latelyMessage;
    map['isWaking'] = session.isWaking ? 1 : 0;
    map['isTop'] = session.isTop ? 1 : 0;
    map['isDisturb'] = session.isDisturb ? 1 : 0;
    map['isWaking2'] = session.isWaking2 ? 1 : 0;
    map['isTop2'] = session.isTop2 ? 1 : 0;
    map['isDisturb2'] = session.isDisturb2 ? 1 : 0;
    map["status"]= session.status?.value;
    map['ext1'] = session.ext1;
    map['ext2'] = session.ext2;
    map['updateTime'] = DateUtil.formatDate(session.updateTime);

    return map;
  }

  ImSession _toImSession(Map<String, Object?> map) {
    var session = ImSession();
    session.id = MapUtil.getInt(map, "id");
    session.name = MapUtil.getString(map, "name");
    session.currentUserId = MapUtil.getString(map, 'currentUserId');
    session.groupCode = MapUtil.getStringOrNull(map, 'groupCode');
    session.fromUserId = MapUtil.getStringOrNull(map, 'fromUserId');
    session.serverId = MapUtil.getStringOrNull(map, 'serverId');
    session.toUserId = MapUtil.getStringOrNull(map, 'toUserId');
    session.hotMessageTotal = MapUtil.getIntOrNull(map, 'hotMessageTotal');
    session.latelyMessage = MapUtil.getStringOrNull(map, 'latelyMessage');
    session.isWaking = MapUtil.getBoolean(map, 'isWaking');
    session.isTop = MapUtil.getBoolean(map, 'isTop');
    session.isDisturb = MapUtil.getBoolean(map, 'isDisturb');
    session.isWaking2 = MapUtil.getBoolean(map, 'isWaking2');
    session.isTop2 = MapUtil.getBoolean(map, 'isTop2');
    session.isDisturb2 = MapUtil.getBoolean(map, 'isDisturb2');
    session.sessionType = ImSessionType.ofValue(MapUtil.getIntOrNull(map, 'sessionType'));
    session.status = ImSessionStatus.ofValue(MapUtil.getIntOrNull(map, 'status'));
    session.ext1 = MapUtil.getStringOrNull(map, 'ext1');
    session.ext2 = MapUtil.getStringOrNull(map, 'ext2');
    session.createTime = MapUtil.getDate(map, "createTime");
    session.updateTime = MapUtil.getDate(map, "updateTime");
    return session;
  }
}